<?php

namespace app\admin\command;

use Carbon\Carbon;
use fast\Random;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\Db;
use think\Env;
use think\Exception;
use think\Log;
use app\admin\model\pt\DutyList as DutyModel;
use app\admin\model\pt\Person as PersonModel;
use app\admin\model\pt\Depart as DepartModel;
use app\admin\model\pt\Job as JobModel;
use app\admin\model\pt\Salary as SalaryModel;
use app\common\library\Salary as SalaryLib;

class CalPersonSalary extends Command
{

    protected $_jobModel = null;
    protected $_departModel = null;
    protected $_personModel = null;
    protected $_salaryModel = null;

    protected function configure()
    {
        $defaultDateId = date('Ymd', strtotime('-1 day'));
        $this->setName('calsalary')
            ->addOption('date', 'd', Option::VALUE_OPTIONAL, 'date', $defaultDateId)
            ->setDescription('update person today job 零点后运行');
    }
    
    protected function execute(Input $input, Output $output)
    {
        @ini_set("memory_limit",'-1');
        $stime=microtime(true);

        $curDate = $input->getOption('date');
        
        $this->_jobModel = new JobModel();
        $this->_departModel = new DepartModel();
        $this->_personModel = new PersonModel();
        $this->_salaryModel = new SalaryModel();

        $this->personInfo = null;
        $this->departInfo = null;
        $this->jobInfo = null;

        $this->salaryInfo = null;

        $this->salaryLib = new SalaryLib();
        
        // 从人员安排表中找出对应日期有工作的人员
        $dutys = DutyModel::where('date_id', $curDate)->with('job')->select();
        $logPre = 'cal person salary';
        // 计算前先从岗位那边把薪资相关信息再做次同步。
        Log::write($logPre.': 同步岗位的薪资信息开始, date =>' . $curDate);
        foreach ($dutys as $d) {
            Db::startTrans();
            try {
                $this->jobInfo = JobModel::where('id', $d->pt_job_id)->find();
                if (!$this->jobInfo) {
                    Log::write($logPre . ': 未找到岗位信息, duty id =>' . $d->id . ',job id =>'. $d->pt_job_id);
                } else {
                    $this->personInfo = $this->_personModel->get($d->pt_person_id);
                    $this->syncSalary($d->id, $d->date_id);
                }
                Db::commit();
            } catch (Exception $e) {
                Db::rollback();
                print_r($e->getTraceAsString());
                Log::write($logPre . ': 同步岗位薪资信息失败, message:'.$e->getMessage() .', duty id =>' . $d->id);
            }
        }
        Log::write($logPre.': 同步岗位的薪资信息结束, date =>' . $curDate);

        Log::write($logPre.': 计算每日薪资开始, date =>' . $curDate);
        foreach ($dutys as $d) {
            Db::startTrans();
            try {
                $this->salaryInfo = $this->_salaryModel->where('duty_id', $d->id)->find();
                $this->personInfo = $this->_personModel->get($d->pt_person_id);
                if (!$this->salaryInfo) {
                    Log::write($logPre . ': 无对应当天的薪酬信息, duty id =>' . $d->id);
                } else {
                    $this->salaryLib->setObject($this->personInfo, null, null, $this->salaryInfo);
                    $checkSelfAndReward = $this->salaryLib->checkSelfAndReward($d->date_id);
                    // 有打卡记录的才计算
                    $isCal = 0;
                    if (!empty($d->clock_json)) {
                        $clockJson = json_decode($d->clock_json, true);  
                        // 取出薪酬记录表中的岗位计算模式
                        $salaryMode = $this->salaryInfo->salary_mode;
                        // 如果是场次，需要有输入场次数字段才计算薪酬
                        $selfServiceFee = !empty($d->job) ? $d->job->self_sevice_fee : 0;
                        $rewardMonth = $this->personInfo->reward_month;
                        if ($salaryMode == 'stage' && isset($clockJson['stage'])) {
                            $calClock = $this->calStage($clockJson, $salaryMode);
                            $salaryPayable = $this->calSalary($calClock, $salaryMode);
                            $salaryRes = $this->calSalary($calClock, $salaryMode);
                            $salaryPayable = $salaryRes[0];
                            $added1Fee = $this->checkAdde1Fee($calClock);
                            $roadFee = $d->road_fee;
                            $salaryTotal = $salaryPayable + $added1Fee + $roadFee + $this->salaryInfo->added2_fee;

                            $this->salaryInfo->salary = $salaryRes[1];
                            $this->salaryInfo->clock_in = $calClock['clock_in']; 
                            $this->salaryInfo->clock_out = $calClock['clock_out']; 
                            $this->salaryInfo->added1_fee = $added1Fee;
                            $this->salaryInfo->road_fee = $roadFee;
                            $this->salaryInfo->salary_payable = $salaryPayable;
                            $this->salaryInfo->actual_duration =  $calClock['actual_duration'];
                            $this->salaryInfo->actual_calc_duration = $calClock['actual_calc_duration'];
                            $this->salaryInfo->salary_total = $salaryTotal;
                            $isCal = 1;
                            // 更新用户的累积场次
                            $this->personInfo->explain_total = $this->personInfo->explain_total + $clockJson['stage'];
                            $this->personInfo->save();
                                        
                            Log::write($logPre . ': 计算人员薪资完成, duty id =>' . $d->id . ', person_id =>' . $d->pt_person_id);
                        } else if ($salaryMode != 'stage' && count($clockJson) >= 2) {
                            // 非场次必须有两次打卡记录
                            $calClock = $this->calClock($clockJson, $salaryMode, $d);
                            $salaryRes = $this->calSalary($calClock, $salaryMode);
                            $salaryPayable = $salaryRes[0];
                            $added1Fee = $this->checkAdde1Fee($calClock);
                            $roadFee = $d->road_fee;
                            $salaryTotal = $salaryPayable + $added1Fee + $roadFee + $this->salaryInfo->added2_fee;

                            $isCal = 1;
                            Log::write($logPre . ': 计算人员薪资完成, duty id =>' . $d->id . ', person_id =>' . $d->pt_person_id);
                        } else {
                            if (isset($clockJson[0])) {
                                $this->salaryInfo->clock_in = $clockJson[0];
                            } else {
                                Log::write($logPre . ': 无打卡记录, duty id =>' . $d->id);
                            }
                            // 其他的都不能计算薪酬
                            Log::write($logPre . ': 不满足计薪打卡条件, duty id =>' . $d->id);
                        }
                    }                
                    // 有计算出薪资
                    if ($isCal) {
                        $this->salaryInfo->clock_in = $calClock['clock_in']; 
                        $this->salaryInfo->clock_out = $calClock['clock_out']; 
                        $this->salaryInfo->added1_fee = $added1Fee;
                        $this->salaryInfo->road_fee = $roadFee;
                        $this->salaryInfo->salary_payable = $salaryPayable;
                        $this->salaryInfo->actual_duration =  $calClock['actual_duration'];
                        $this->salaryInfo->actual_calc_duration = $calClock['actual_calc_duration'];
                        $this->salaryInfo->self_sevice_fee = 0;
                        $this->salaryInfo->reward_month = 0;
                        // 判断如果是自招的才会有自招兼职服务费
                        if ($checkSelfAndReward[0] == 0 && $this->personInfo->user_type != 'labor') {
                            $salaryTotal += $selfServiceFee;
                            $this->salaryInfo->self_sevice_fee = $selfServiceFee;
                        }
                        if ($checkSelfAndReward[1] == 0) {
                            $salaryTotal += $rewardMonth;
                            $this->salaryInfo->reward_month = $rewardMonth;
                        }
                        $this->salaryInfo->salary_total = $salaryTotal;
                    }
                    $this->salaryInfo->is_cal = 1;
                    $this->salaryInfo->save();
                }
                Db::commit();
            } catch (Exception $e) {
                Db::rollback();
                print_r($e->getTraceAsString());
                Log::write($logPre . ': 人员信息失败, message:'.$e->getMessage() .', duty id =>' . $d->id);
            }
            
        }
        Log::write($logPre . ': 人员信息更新结束, date =>' . $curDate);



        $etime=microtime(true);//获取程序执行结束的时间
        $total=$etime-$stime;   //计算差值
        $output->writeln($total);
        $output->info('update person job success');
    }
    
    // 计算餐费规则
    // 餐补规则： 实际出勤时长<应出勤时长的65%，则餐费为0
    // 场次餐补填写啥返回啥
    private function checkAdde1Fee($calClock)
    {
        return $this->salaryLib->checkAdde1Fee($calClock);
        /*
        $added1Fee = $this->salaryInfo->added1_fee; 
        $requireDuration = $this->salaryInfo->require_duration;
        if (!empty($calClock['clock_in'])) {
            $s = 65;
            $d = ceil($calClock['actual_duration'] / $requireDuration * 100);
            if ($d < $s) {
                $added1Fee = 0;
            }
        }

        return $added1Fee;
         */
    }
    
    // 计算工资 
    private function calSalary($calClock, $salaryMode)
    {
        return $this->salaryLib->calSalary($calClock, $salaryMode);
        /*
        $salary = 0;
        $calSalary = $this->salaryInfo->salary;
        if ($salaryMode == 'hour') {
            $salary = round($calSalary * $calClock['actual_calc_duration'], 2);
        }
        if ($salaryMode == 'day') {
            $salary = round($calSalary * $calClock['actual_calc_duration'], 2);
        }
        if ($salaryMode == 'stage') {
            $salaryRule = $this->salaryInfo->salary_rule;  
            // 加上当前场次后累积计算
            $personStages = $this->personInfo->explain_total + $calClock['stage'];
            // 非劳务公司计算
            if (!empty($salaryRule)) {
                $ruleJson = json_decode($salaryRule, true);
                foreach ($ruleJson as $rule) {
                    // 目前是两边都加上等于
                    $end = $rule['end'];
                    if (empty($end)) {
                        $end = 99999;
                    }
                    if ($personStages >= $rule['start'] && $personStages <= $end) {
                        $calSalary= $rule['salary'];
                        break;
                    }
                }
            }
            $salary = round($calSalary * $calClock['actual_calc_duration'], 2);
        }
        // 场次工资
        return [$salary, $calSalary];
         */
    }
    
    // 计算打卡时间, 根据以下规则计算实际出勤时间和应计薪时长
    // 默认9点， 9点以前， 都算从9点算工资， 9点以后， 按后面实际时间计算开始
    /*
     * 第一次打卡时间=C1 最后一次打卡时间=C2
        应出勤时长=A
        应出计薪时长=B
        实际出勤时长=C2-C1=C
        实际计薪时长=C-(A-B)=D

        按小时工资时 = D * 小时工资
        按天工资：5：30后打卡，然后D>B 按一天工资，D<B, D/B计算比例后乘以一天工资

     */
    private function calClock($clockJson, $salaryMode, $duty = null)
    {
        return $this->salaryLib->calClock($clockJson, $salaryMode, $duty);
        /*
        $calRes = [
            'clock_in' => 0,
            'clock_out' => 0,
            'actual_duration' => 0,
            'actual_calc_duration' => 0,
        ];
        // 应出勤时长 
        $requireDuration = $this->salaryInfo->require_duration;
        // 应计薪时长
        $requireCalcDuration = $this->salaryInfo->require_calc_duration;
        // 差值时长, 后面计算应计薪时长需要
        $diffDuration = $requireDuration - $requireCalcDuration;
        if (empty($requireCalcDuration)) {
            return $calRes;
        }
        
        // 将该时间换成设置的最早时间 
        $starttime = "09:00:00";
        if (!empty($this->salaryInfo->max_start)) {
            $starttime = $this->salaryInfo->max_start . ":00";
        }
        $endtime = "17:30:00";
        asort($clockJson);
        $clockIn = $clockJson[0];
        $clockOut = $clockJson[count($clockJson) - 1];
        $calRes['clock_in'] = $clockIn;
        $calRes['clock_out'] = $clockOut;
        $curDate = date('Y-m-d', $clockIn);
        
        // 应打卡开始时间
        $startClock = strtotime($curDate . " " . $starttime);
        // 应打卡结束时间 
        $endClock = strtotime($curDate . " " . $endtime);
        
        // 可用于计算的打卡开始时间 
        $calStartClock = $startClock;
        // 如果开始打卡大于定义的开始时间
        if ($clockIn > $startClock) {
            // 如果有设置抹零分钟数
            if (!empty($duty->ignore_mins)) {
                $ignoreSecs = $duty->ignore_mins * 60;
                // 抹零后的秒数
                $clockIn = $clockIn - $ignoreSecs;
                if ($clockIn > $startClock) {
                    $calStartClock = $clockIn;
                } else {
                    // 达到抹零要求时，将打卡开始时间改为最早打卡时间记录
                    $calRes['clock_in'] = $startClock;
                }
            } else {
                $calStartClock = $clockIn;
            }
        }
        // 可用于计算的打卡结束时间 
        $calEndClock = $clockOut;
        $startCarbon = Carbon::parse($calStartClock);
        $endCarbon = Carbon::parse($calEndClock);

        $diffHours = $endCarbon->diffInHours($startCarbon);
        $diffFloatHours = $endCarbon->floatDiffInHours($startCarbon);
        // 实际出勤时长, 保留2位，这样显示时能更清楚看到 
        $actualDuration = round($diffFloatHours, 2);
        $mins = ceil(($diffFloatHours - $diffHours) * 60);
        if ($mins > 30) {
            $diffHours = $diffHours + 0.5;
        }
        // 应计薪时长
        $actualCalcDuration = round($diffHours - $diffDuration, 1);
        // 如果是小时工资
        if ($salaryMode == 'hour') {
            $calRes['actual_duration'] = $actualDuration;
            $calRes['actual_calc_duration'] = $actualCalcDuration;
        }
        // 如果是按天计算工资 
        if ($salaryMode == 'day') {
            $calRes['actual_duration'] = $actualDuration;
            if ($actualCalcDuration >= $requireCalcDuration) {
                $calRes['actual_calc_duration'] = 1;
            } else {
                // 计算比例
                $calRes['actual_calc_duration'] = round($actualCalcDuration / $requireCalcDuration, 1);
            }
        }

        return $calRes;
        */


    }

    private function calStage($clockJson)
    {
        return $this->salaryLib->calStage($clockJson);
        /*
        $calRes = [
            'clock_in' => 0,
            'clock_out' => 0,
            'actual_duration' => 0,
            'actual_calc_duration' => 0,
        ];
        $salaryRule = $this->salaryInfo->salary_rule;  
        $personStages = $this->personInfo->explain_total;
        // 加上本次讲解场次后根据场次规则计算
        $calRes['actual_duration'] = $clockJson['stage'];
        $calRes['actual_calc_duration']= $clockJson['stage'];

        return $calRes;
         */
    }

    private function syncSalary($dutyId, $dateId)
    {
        $params = [];
        $params['salary_mode'] = $this->jobInfo->salary_mode;
        $params['max_start'] = $this->jobInfo->max_start;
        // 根据人员和岗位对比后填入
        $salaryInfo = $this->checkSalary();
        if (empty($salaryInfo['salary']) && empty($salaryInfo['salary_rule'])) {
            Log::write('没有获取到薪水规则信息, job  =>' . var_export($this->jobInfo->toArray(), true));
            return ;
        }
        $params['salary_rule'] = $salaryInfo['salary_rule'];
        $params['salary'] = $salaryInfo['salary'];

        // 餐费这里先快照进来，然后每天计算薪酬的脚本根据时长判断是否改为0
        $params['added1_fee'] = $this->jobInfo->added1_fee;
        $params['added2_fee'] = $this->jobInfo->added2_fee;
        $params['require_duration'] = $this->jobInfo->attendance_duration;
        $params['require_calc_duration'] = $this->jobInfo->calc_duration;
        $salaryInfo = $this->_salaryModel->where('pt_person_id', $this->personInfo->id)
                                         ->where('date_id', $dateId)
                                         ->where('duty_id', $dutyId)
                                         ->find();
        // 因为在工作安排的时已插入数据，所以这里只判断更新岗位的薪资信息数据
        if ($salaryInfo) {
            $params['id'] = $salaryInfo->id;
            $salaryInfo->save($params);
        }
    }

    private function checkSalary()
    {
        $salaryInfo = [
            'salary'  => 0, 
            'salary_rule' => '',
        ];
        // 获取人员类别
        $personType = $this->personInfo->user_type; 
        $laborId = $this->personInfo->pt_labor_id;
        // 劳务公司
        if ($personType == 'labor') {
            // 判断劳务公司
            $jobInfo = $this->jobInfo->toArray();
            // 判断薪资模式
            switch($jobInfo['salary_mode']) {
                case 'hour':
                  $salaryInfo['salary'] = $jobInfo['labor' . $laborId . '_salary'];
                  break;
                case 'day':
                  $salaryInfo['salary'] = $jobInfo['labor' . $laborId . '_salary_day'];
                  break;
                case 'stage':
                  $salaryInfo['salary_rule'] = $jobInfo['labor' . $laborId .'_salary_stage'];
                  break;
            }
        } else {
            // 其他都算自招 根据薪资模式取值
            switch($this->jobInfo->salary_mode) {
                case 'hour':
                  $salaryInfo['salary'] = $this->jobInfo->self_salary;
                  break;
                case 'day':
                  $salaryInfo['salary'] = $this->jobInfo->self_salary_day;
                  break;
                case 'stage':
                  $salaryInfo['salary'] = $this->jobInfo->self_salary_stage;
                  break;
            }
        }

        return $salaryInfo;
    }
}
